﻿namespace TestTool.Tests.Common.Discovery
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.IO;
    using System.Linq;
    using System.Net;
    using System.Net.Sockets;
    using System.Reflection;
    using System.Runtime.CompilerServices;
    using System.Text;
    using System.Threading;
    using System.Xml;
    using System.Xml.Schema;
    using System.Xml.Serialization;
    using TestTool.Proxies.WSDiscovery;
    using TestTool.Tests.Common.Soap;

    public class Discovery : IDisposable
    {
        private Thread _connectionThread;
        private List<XmlSchema> _discoverySchemas;
        private IPEndPoint _endPointLocal;
        private IPAddress _listenAddress;
        private string _listenDevice;
        private List<string> _listenMessages;
        private List<DiscoverySocketEventArgs> _packetsReceived;
        private bool _parseLater;
        private ProcessMessage _processMessageMethod;
        private DiscoverySocket _socket;
        private object _socketSync;
        private AutoResetEvent _stopListenEvent;
        private const string SCOPE_MATCH_BY_URL = "http://schemas.xmlsoap.org/ws/2005/04/discovery/rfc3986";
        private IPAddress WS_DISCOVER_MULTICAST_IP4;
        private IPAddress WS_DISCOVER_MULTICAST_IP6;
        private const int WS_DISCOVER_PORT = 0xe76;
        private const int WS_DISCOVER_TIMEOUT = 0xbb8;

        public event EventHandler<DiscoveryMessageEventArgs> ByeReceived;

        public event EventHandler<DiscoveryMessageEventArgs> Discovered;

        public event EventHandler DiscoveryFinished;

        public event EventHandler<DiscoveryMessageEventArgs> HelloReceived;

        public event EventHandler<MessageEventArgs> MessageSent;

        public event EventHandler<DiscoveryErrorEventArgs> ReceiveError;

        public event EventHandler<DiscoveryErrorEventArgs> SoapFaultReceived;

        public Discovery(IPAddress local)
        {
            this.WS_DISCOVER_MULTICAST_IP4 = IPAddress.Parse("239.255.255.250");
            this.WS_DISCOVER_MULTICAST_IP6 = IPAddress.Parse("FF02::C");
            this._socketSync = new object();
            this._listenMessages = new List<string>();
            this._stopListenEvent = new AutoResetEvent(false);
            this._packetsReceived = new List<DiscoverySocketEventArgs>();
            this._endPointLocal = new IPEndPoint(local, 0xe76);
            Stream manifestResourceStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("TestTool.Tests.Common.Discovery.Schemas.ws-discovery.xsd");
            XmlSchema schema = XmlSchema.Read(manifestResourceStream, null);
            manifestResourceStream.Close();
            manifestResourceStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("TestTool.Tests.Common.Discovery.Schemas.addressing.xsd");
            XmlSchema schema2 = XmlSchema.Read(manifestResourceStream, null);
            manifestResourceStream.Close();
            this._discoverySchemas = new List<XmlSchema> { schema, schema2 };
        }

        public Discovery(IPAddress local, ProcessMessage processMessageMethod) : this(local)
        {
            this._processMessageMethod = processMessageMethod;
        }

        protected byte[] BuildProbeMessage(DiscoveryUtils.DiscoveryType[] types, string[] scopes, string matchRule)
        {
            ProbeType type = new ProbeType {
                Scopes = new ScopesType()
            };
            type.Scopes.MatchBy = matchRule;
            if (scopes != null)
            {
                string str = string.Empty;
                for (int i = 0; i < scopes.Length; i++)
                {
                    str = str + scopes[i];
                    if (i < (scopes.Length - 1))
                    {
                        str = str + " ";
                    }
                }
                type.Scopes.Text = new string[] { str };
            }
            XmlSerializerNamespaces namespaces = new XmlSerializerNamespaces();
            int num2 = 0;
            string str2 = string.Empty;
            bool flag = true;
            if (types != null)
            {
                foreach (DiscoveryUtils.DiscoveryType type2 in types)
                {
                    string prefix;
                    string ns = type2.Namespace;
                    string str4 = type2.Type;
                    if (string.IsNullOrEmpty(type2.Prefix))
                    {
                        prefix = string.Format("ns{0}", num2);
                        num2++;
                    }
                    else
                    {
                        prefix = type2.Prefix;
                    }
                    namespaces.Add(prefix, ns);
                    if (flag)
                    {
                        flag = false;
                    }
                    else
                    {
                        str2 = str2 + " ";
                    }
                    str2 = str2 + prefix + ":" + str4;
                }
            }
            type.Types = str2;
            return SoapBuilder.BuildMessage(type, Encoding.UTF8, new DiscoveryHeaderBuilder(), namespaces);
        }

        public void Close()
        {
            if (this._connectionThread != null)
            {
                this._connectionThread.Abort();
                this._connectionThread.Join();
            }
            lock (this._socketSync)
            {
                if (this._socket != null)
                {
                    this._socket.Close();
                }
            }
        }

        protected void CloseConnection(object state)
        {
            int timeout = ((CloseConnectionState) state).Timeout;
            Trace.WriteLine(string.Format("{0} CloseConnection - started, timeout={1}", DateTime.Now.ToLongTimeString(), timeout));
            Trace.Flush();
            bool flag = false;
            if ((this._listenAddress == null) && (this._listenDevice == null))
            {
                Thread.Sleep(timeout);
                flag = true;
            }
            else
            {
                flag = !this._stopListenEvent.WaitOne(timeout);
            }
            Trace.WriteLine(string.Format("{0} CloseConnection - awaken {1}", DateTime.Now.ToLongTimeString(), flag ? "after timeout" : "by event"));
            Trace.Flush();
            lock (this._socketSync)
            {
                if (this._socket != null)
                {
                    this._socket.Close();
                    this._socket = null;
                }
            }
            foreach (DiscoverySocketEventArgs args in this._packetsReceived)
            {
                this.ProcessIncomingPacket<ProbeMatchesType>(args, delegate {
                });
            }
            if (this.DiscoveryFinished != null)
            {
                this.DiscoveryFinished(this, EventArgs.Empty);
            }
        }

        public void Dispose()
        {
            this.Close();
        }

        protected IPAddress GetDiscoveryMulticastAddress()
        {
            if (this._endPointLocal.AddressFamily != AddressFamily.InterNetworkV6)
            {
                return this.WS_DISCOVER_MULTICAST_IP4;
            }
            return this.WS_DISCOVER_MULTICAST_IP6;
        }

        protected EventHandler<DiscoveryMessageEventArgs> GetHandler(Type messageType)
        {
            EventHandler<DiscoveryMessageEventArgs> byeReceived = null;
            if (typeof(ProbeMatchesType).GUID == messageType.GUID)
            {
                return this.Discovered;
            }
            if (typeof(HelloType).GUID == messageType.GUID)
            {
                return this.HelloReceived;
            }
            if (typeof(ByeType).GUID == messageType.GUID)
            {
                byeReceived = this.ByeReceived;
            }
            return byeReceived;
        }

        protected bool IsExpectedMessage<T>(SoapMessage<T> message) where T: class
        {
            if (!this.IsExpectedMessageHeader(message.Header))
            {
                return false;
            }
            if (!string.IsNullOrEmpty(this._listenDevice))
            {
                string deviceId = string.Empty;
                if (message.Object is ProbeMatchesType)
                {
                    deviceId = DiscoveryUtils.GetDeviceId(message.Object as ProbeMatchesType);
                }
                else if (message.Object is HelloType)
                {
                    deviceId = DiscoveryUtils.GetDeviceId(message.Object as HelloType);
                }
                else if (message.Object is ByeType)
                {
                    deviceId = DiscoveryUtils.GetDeviceId(message.Object as ByeType);
                }
                if (this._listenDevice != deviceId)
                {
                    return false;
                }
            }
            return true;
        }

        protected bool IsExpectedMessageHeader(ICollection<XmlElement> header)
        {
            if (this._listenMessages.Count<string>() <= 0)
            {
                return true;
            }
            string b = DiscoveryUtils.ExtractRelatesTo(header);
            foreach (string str2 in this._listenMessages)
            {
                if (DiscoveryUtils.CompareUUID(str2, b))
                {
                    return true;
                }
            }
            return false;
        }

        protected bool IsMulticast(IPAddress address)
        {
            byte[] addressBytes = address.GetAddressBytes();
            return (address.IsIPv6Multicast || ((addressBytes[0] >= 0xe0) && (addressBytes[0] <= 0xef)));
        }

        protected void JoinDiscoveryMutlicastGroup(DiscoverySocket socket)
        {
            IPAddress discoveryMulticastAddress = this.GetDiscoveryMulticastAddress();
            socket.JoinMulticastGroup(discoveryMulticastAddress);
        }

        protected void OnMessageReceived<T>(object sender, DiscoverySocketEventArgs e) where T: class
        {
            Action action = null;
            if (this._parseLater)
            {
                DiscoverySocketEventArgs item = new DiscoverySocketEventArgs {
                    Source = new IPEndPoint(e.Source.Address, e.Source.Port),
                    Message = new byte[e.Message.Count<byte>()]
                };
                e.Message.CopyTo(item.Message, 0);
                this._packetsReceived.Add(item);
            }
            else
            {
                if (action == null)
                {
                    action = () => this._stopListenEvent.Set();
                }
                this.ProcessIncomingPacket<T>(e, action);
            }
        }

        public void Probe(DiscoveryUtils.DiscoveryType[][] types)
        {
            this.Probe(true, null, null, 0xbb8, types, null);
        }

        public void Probe(DiscoveryUtils.DiscoveryType[][] types, string[] scopes)
        {
            this.Probe(true, null, null, 0xbb8, types, scopes);
        }

        public void Probe(IPAddress address, DiscoveryUtils.DiscoveryType[][] types)
        {
            this.Probe(false, address, null, 0xbb8, types, null);
        }

        public void Probe(IPAddress address, DiscoveryUtils.DiscoveryType[][] types, string[] scopes)
        {
            this.Probe(false, address, null, 0xbb8, types, scopes);
        }

        public void Probe(bool multicast, IPAddress address, string deviceId, DiscoveryUtils.DiscoveryType[][] types)
        {
            this.Probe(multicast, address, deviceId, 0xbb8, types, null);
        }

        public void Probe(bool multicast, IPAddress address, string deviceId, int timeout, DiscoveryUtils.DiscoveryType[][] types, string[] scopes)
        {
            this.Probe(multicast, address, deviceId, timeout, types, scopes, null);
        }

        public void Probe(bool multicast, IPAddress address, string deviceId, int timeout, DiscoveryUtils.DiscoveryType[][] types, string[] scopes, string matchRule)
        {
            this._socket = new DiscoverySocket(this._endPointLocal);
            this._socket.MessageReceived += new EventHandler<DiscoverySocketEventArgs>(this.OnMessageReceived<ProbeMatchesType>);
            try
            {
                if (multicast)
                {
                    this.JoinDiscoveryMutlicastGroup(this._socket);
                }
                IPEndPoint destination = multicast ? new IPEndPoint(this.GetDiscoveryMulticastAddress(), 0xe76) : new IPEndPoint(address, 0xe76);
                List<byte[]> sendBytes = new List<byte[]>();
                List<string> list2 = new List<string>();
                if (types != null)
                {
                    foreach (DiscoveryUtils.DiscoveryType[] typeArray in types)
                    {
                        byte[] message = this.BuildProbeMessage(typeArray, scopes, matchRule);
                        list2.Add(DiscoveryUtils.ExtractMessageId(message));
                        sendBytes.Add((this._processMessageMethod != null) ? this._processMessageMethod(message) : message);
                    }
                }
                else
                {
                    byte[] buffer2 = this.BuildProbeMessage(null, scopes, matchRule);
                    list2.Add(DiscoveryUtils.ExtractMessageId(buffer2));
                    sendBytes.Add((this._processMessageMethod != null) ? this._processMessageMethod(buffer2) : buffer2);
                }
                sendBytes.Reverse();
                this._socket.Send(destination, sendBytes);
                this.StartListen(timeout, address, deviceId, list2.ToArray(), true);
                foreach (byte[] buffer3 in sendBytes)
                {
                    if (this.MessageSent != null)
                    {
                        string str = Encoding.UTF8.GetString(buffer3);
                        MessageEventArgs e = new MessageEventArgs {
                            Message = str
                        };
                        this.MessageSent(this, e);
                    }
                }
            }
            catch (Exception)
            {
                this._socket.Close();
                throw;
            }
        }

        protected void ProcessIncomingPacket<T>(DiscoverySocketEventArgs e, Action action) where T: class
        {
            if ((this._listenAddress == null) || DiscoveryUtils.CompareAddresses(e.Source.Address, this._listenAddress))
            {
                try
                {
                    SoapMessage<T> message = SoapBuilder.ParseMessage<T>(e.Message, this._discoverySchemas);
                    if (this.IsExpectedMessage<T>(message))
                    {
                        EventHandler<DiscoveryMessageEventArgs> handler = this.GetHandler(message.Object.GetType());
                        DiscoveryMessageEventArgs args = new DiscoveryMessageEventArgs(message.ToSoapMessage<object>(), e.Source.Address);
                        if (handler != null)
                        {
                            handler(this, args);
                        }
                        action();
                    }
                }
                catch (SoapFaultException exception)
                {
                    if ((exception.Message != null) && this.IsExpectedMessage<Fault>(exception.FaultMessage))
                    {
                        if (this.SoapFaultReceived != null)
                        {
                            this.SoapFaultReceived(this, new DiscoveryErrorEventArgs(exception, exception.Fault));
                        }
                        action();
                    }
                }
                catch (UnxpectedElementException exception2)
                {
                    if ((this._listenMessages.Count<string>() > 0) && this.IsExpectedMessageHeader(exception2.Headers))
                    {
                        if (this.ReceiveError != null)
                        {
                            this.ReceiveError(this, new DiscoveryErrorEventArgs(exception2, null));
                        }
                        action();
                        throw;
                    }
                }
                catch (Exception exception3)
                {
                    if (this.ReceiveError != null)
                    {
                        this.ReceiveError(this, new DiscoveryErrorEventArgs(exception3, null));
                    }
                    Trace.WriteLine(string.Format("Discovery::OnMessageReceived error [{0}]", exception3.Message));
                    Trace.Flush();
                }
            }
        }

        protected void StartListen(int timeout, IPAddress address, string device, string[] messageIds, bool parseLater)
        {
            Trace.WriteLine(string.Format("{0} Discovery StartListen", DateTime.Now));
            Trace.Flush();
            this._listenAddress = address;
            this._listenDevice = device;
            this._listenMessages.Clear();
            if (messageIds != null)
            {
                foreach (string str in messageIds)
                {
                    if (!string.IsNullOrEmpty(str))
                    {
                        this._listenMessages.Add(str);
                    }
                }
            }
            this._packetsReceived.Clear();
            this._parseLater = parseLater;
            this._stopListenEvent.Reset();
            this._socket.Listen();
            this._connectionThread = new Thread(new ParameterizedThreadStart(this.CloseConnection));
            Trace.WriteLine(string.Format("{0} StartListen, timeout={1}", DateTime.Now.ToLongTimeString(), timeout));
            Trace.Flush();
            CloseConnectionState parameter = new CloseConnectionState {
                Timeout = timeout
            };
            this._connectionThread.Start(parameter);
        }

        public void WaitBye(IPAddress from, string deviceId, int timeout)
        {
            this.WaitMessage(true, from, deviceId, timeout, new EventHandler<DiscoverySocketEventArgs>[] { new EventHandler<DiscoverySocketEventArgs>(this.OnMessageReceived<ByeType>) });
        }

        public void WaitByeOrHello(IPAddress from, string deviceId, int timeout)
        {
            this.WaitMessage(true, from, deviceId, timeout, new EventHandler<DiscoverySocketEventArgs>[] { new EventHandler<DiscoverySocketEventArgs>(this.OnMessageReceived<HelloType>), new EventHandler<DiscoverySocketEventArgs>(this.OnMessageReceived<ByeType>) });
        }

        public void WaitHello(IPAddress from, string deviceId, int timeout)
        {
            this.WaitMessage(true, from, deviceId, timeout, new EventHandler<DiscoverySocketEventArgs>[] { new EventHandler<DiscoverySocketEventArgs>(this.OnMessageReceived<HelloType>) });
        }

        protected void WaitMessage(bool multicast, IPAddress from, string deviceId, int timeout, EventHandler<DiscoverySocketEventArgs>[] callbacks)
        {
            this._socket = new DiscoverySocket(this._endPointLocal);
            if (multicast)
            {
                this.JoinDiscoveryMutlicastGroup(this._socket);
            }
            foreach (EventHandler<DiscoverySocketEventArgs> handler in callbacks)
            {
                this._socket.MessageReceived += handler;
            }
            this.StartListen(timeout, from, deviceId, null, false);
        }

        public delegate byte[] ProcessMessage(byte[] message);
    }
}

